using System;
using System.Configuration;
using System.IO;
using System.Reflection;
using System.Text;
using System.Web;
using System.Web.Configuration;

namespace Seaskyer.WebApp.Utility.StrongUpload
{
	/// <summary>
	/// The HttpUploadModule is the core of SlickUpload. 
	/// </summary>
	public sealed class HttpUploadModule : IHttpModule
	{
		void IHttpModule.Dispose()
		{

		}

		void IHttpModule.Init(HttpApplication context)
		{
			context.BeginRequest += new EventHandler(context_BeginRequest);
			context.Error += new EventHandler(context_Error);
			context.EndRequest += new EventHandler(context_EndRequest);
		}

		private void context_BeginRequest(object sender, EventArgs e)
		{
			HttpApplication app = sender as HttpApplication;

			if (IsUploadRequest(app.Request))
			{
				HttpWorkerRequest worker = GetWorkerRequest(app.Context);
				Encoding encoding = app.Context.Request.ContentEncoding;
				
				if (worker != null)
				{
					byte[] boundary = ExtractBoundary(app.Request.ContentType, encoding);
				
					string uploadId = app.Request.QueryString["uploadId"];

					// If the upload is too big, drop it.
					if (app.Request.ContentLength > GetMaxRequestLength(app.Context))// || MaxRequestLength == -1)
					{
						//worker.SendStatus(200, "OK");

						//byte[] responseData;

						//responseData = Encoding.ASCII.GetBytes("<html><head><title>some title</title></head><body>some test text</body></html>");
						//worker.SendResponseFromMemory(responseData, responseData.Length);
						//worker.
						// TODO: think about subtracting a few K to make sure the headers don't overflow
						//byte[] bla = worker.GetQueryStringRawBytes();

						//worker.FlushResponse(true);
						//app.Request.
						//worker.SendStatus();

						//worker.EndOfRequest();
						app.CompleteRequest();
						
						worker.CloseConnection();

						//throw new IOException("bla");

						//throw new HttpException();
						//string temp2 = encoding.GetString(boundary);
						//string temp1 = temp2 + "\r\n\r\n" + "--" + temp2;
						//Type reqType = app.Request.GetType();

						//FieldInfo info = reqType.GetField("_contentLength", BindingFlags.Instance | BindingFlags.NonPublic);

						//info.SetValue(app.Request, 1);

						//byte[] temp = encoding.GetBytes(" ");
						//InjectTextParts(worker, temp);
						//worker.SendStatus(200, "Ok");
						//app.Response.Redirect(app.Request.Url.ToString());

						UploadStatus status = new UploadStatus(long.Parse(worker.GetKnownRequestHeader(HttpWorkerRequest.HeaderContentLength)), uploadId);

						status.SetState(UploadState.Error);

						RegisterIn(app.Context,  status);

						return;
					}

					try
					{
						MimeUploadHandler handler = new MimeUploadHandler(new RequestStream(worker), boundary, uploadId, encoding);

						if (uploadId != null)
							RegisterIn(app.Context, handler);
						
						SetUploadState(app.Context, UploadState.ReceivingData);
						
						handler.Parse();

						InjectTextParts(worker, encoding.GetBytes(handler.TextParts));
					}
					catch (DisconnectedException)
					{
						//FileStream s = File.OpenWrite(@"c:\disc");
						//s.Close();
						CleanupFiles(app.Context);

						// TODO: inject a blank session
					}
				}
			}
			else
			{
				// TODO: also detect progress requests coming in
			}
		}

		private void context_Error(object sender, EventArgs e)
		{
			HttpApplication app = sender as HttpApplication;

			if (IsUploadRequest(app.Request))
			{
				SetUploadState(app.Context, UploadState.Error);
				CleanupFiles(app.Context);
			}
		}

		private void context_EndRequest(object sender, EventArgs e)
		{
			HttpApplication app = sender as HttpApplication;

			if (IsUploadRequest(app.Request))
			{
				SetUploadState(app.Context, UploadState.Complete);
				CleanupFiles(app.Context);
			}

			string uploadId = (string)app.Context.Items["__removeUploadStatus"];

			if (uploadId != null && uploadId.Length > 0)
			{
				RemoveFrom(app.Application, uploadId);
			}
		}

		void InjectTextParts(HttpWorkerRequest request, byte[] textParts)
		{
			BindingFlags bindingFlags = BindingFlags.Instance | BindingFlags.NonPublic; 
			
			Type type = request.GetType(); 
			
			while ((type != null) && (type.FullName != "System.Web.Hosting.ISAPIWorkerRequest"))
			{ 
				type = type.BaseType; 
			}

			if (type != null)
			{
				type.GetField("_contentAvailLength", bindingFlags).SetValue(request, textParts.Length); 
				type.GetField("_contentTotalLength", bindingFlags).SetValue(request, textParts.Length);
				type.GetField("_preloadedContent", bindingFlags).SetValue(request, textParts); 
				type.GetField("_preloadedContentRead", bindingFlags).SetValue(request, true);
			}
		}

		void CleanupFiles(HttpContext context)
		{
			MimeUploadHandler handler = GetUploadHandler(context);

			if (handler != null)
			{
				// Delete all files
				foreach (UploadedFile file in handler.UploadedFiles)
					File.Delete(file.ServerPath);

				handler.UploadedFiles.Clear();
			}
		}

		byte[] ExtractBoundary(string contentType, Encoding encoding)
		{
			int pos = contentType.IndexOf("boundary=");

			if (pos > 0)
				return encoding.GetBytes("--" + contentType.Substring(pos + 9));
			else
				return null;
		}

		HttpWorkerRequest GetWorkerRequest(HttpContext context)
		{
			/*			Type c = context.GetType();

						PropertyInfo p = c.GetProperty("WorkerRequest",
							BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.Instance);

						if (p != null)
							return (HttpWorkerRequest)p.GetValue(context, null);
						else
							return null;
			*/
			IServiceProvider provider = (IServiceProvider)HttpContext.Current;

			return (HttpWorkerRequest)provider.GetService(typeof(HttpWorkerRequest));
		}

		bool IsUploadRequest(HttpRequest request)
		{
			return request.ContentType.ToLower().StartsWith("multipart/form-data");
		}

		/// <summary>
		/// 
		/// </summary>
		/// <returns>A collection containing all files uploaded in the current context.</returns>
		public static UploadedFileCollection GetUploadedFiles()
		{
			return GetUploadedFiles(HttpContext.Current);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="context">The <see>HttpContext</see> to reference.</param>
		/// <returns>A collection containing all files uploaded in the specified context.</returns>
		public static UploadedFileCollection GetUploadedFiles(HttpContext context)
		{
			MimeUploadHandler handler = (MimeUploadHandler)context.Items["_uploadHandler"];

			if (handler != null)
			{
				return UploadedFileCollection.ReadOnly(handler.UploadedFiles);
			}
			else
			{
				return null;
			}
		}

		/// <summary>
		/// Returns the upload status associated with the current <see cref="HttpContext">HttpContext</see>.
		/// </summary>
		/// <returns>The upload status associated with the current <see cref="HttpContext" />, or null if none exists.</returns>
		public static UploadStatus GetUploadStatus()
		{
			return GetUploadStatus(HttpContext.Current);
		}

		/// <summary>
		/// Returns the upload status associated with a specified <see cref="HttpContext" />.
		/// </summary>
		/// <param name="context">The context from which to look up the upload status.</param>
		/// <returns>The upload status associated with the specified <see cref="HttpContext">HttpContext</see>, or null if none exists.</returns>
		public static UploadStatus GetUploadStatus(HttpContext context)
		{
			return GetUploadStatus((string)context.Request.QueryString["uploadId"]);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="uploadId"></param>
		/// <returns></returns>
		public static UploadStatus GetUploadStatus(string uploadId)
		{
			HttpContext context = HttpContext.Current;

			UploadStatus status = GetUploadStatus(context.Application, uploadId);

			if (status != null)
			{
				if (status.State != UploadState.ReceivingData && status.AutoDropState)
				{
					context.Items["__removeUploadStatus"] = uploadId;
				}
			}

			return status;
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="application">The <see>HttpApplicationState</see> to reference.</param>
		/// <param name="uploadId">The unique id of the upload.</param>
		/// <returns></returns>
		public static UploadStatus GetUploadStatus(HttpApplicationState application, string uploadId)
		{
			UploadStatus status = (UploadStatus)application["_UploadStatus_" + uploadId];

			return status;
		}

		void SetUploadState(HttpContext context, UploadState state)
		{
			MimeUploadHandler handler = GetUploadHandler(context);

			if (handler != null)
			{
				handler.UploadStatus.SetState(state);
			}
		}

		void RegisterIn(HttpContext context, MimeUploadHandler handler)
		{
			context.Items["_uploadHandler"] = handler;

			RegisterIn(context, handler.UploadStatus);
		}

		void RegisterIn(HttpContext context, UploadStatus status)
		{
			context.Application["_UploadStatus_" + status.UploadId] = status;			
		}

		void ClearUploadStatus()
		{
			RemoveFrom(HttpContext.Current.Application, GetUploadStatus().UploadId);
		}

		MimeUploadHandler GetUploadHandler(HttpContext context)
		{
			return (MimeUploadHandler)context.Items["_uploadHandler"];
		}

		int GetMaxRequestLength(HttpContext context)
		{
			//get
			//{
				object httpRuntimeConfig = context.GetConfig("system.web/httpRuntime");

				Type configType = httpRuntimeConfig.GetType();

				if (configType != null)
				{
					PropertyInfo propInfo = configType.GetProperty("MaxRequestLength", BindingFlags.Instance | BindingFlags.NonPublic);

					if (propInfo != null)
					{
						return (int)propInfo.GetValue(httpRuntimeConfig, null);
					}
				}
				/*try
				{
					string maxRequestLength = ConfigurationSettings.AppSettings["maxRequestLength"];

					return int.Parse(maxRequestLength);
				}
				catch
				{*/
					// Not found for some reason
					return -1;
				//}
			//}
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="uploadId">The unique id of the upload.</param>
		public static void RemoveFrom(string uploadId)
		{
			RemoveFrom(HttpContext.Current.Application, uploadId);
		}

		/// <summary>
		/// 
		/// </summary>
		/// <param name="application">The <see>HttpApplicationState</see> to reference.</param>
		/// <param name="uploadId">The unique id of the upload.</param>
		public static void RemoveFrom(HttpApplicationState application, string uploadId)
		{
			application.Remove("_UploadStatus_" + uploadId);
		}
	}
}